package com.springboot.tools.generator;

import com.springboot.tools.annotations.IdType;
import com.springboot.tools.exceptions.MybatisPlusException;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class AutoGenerator {
    private ConfigGenerator config;

    public ConfigGenerator getConfig() {
        return config;
    }

    public void setConfig(ConfigGenerator config) {
        this.config = config;
    }

    public AutoGenerator(ConfigGenerator config) {
        super();
        this.config = config;
    }

    public AutoGenerator() {

    }

    private static String PATH_ENTITY = null;
    private static String PATH_MAPPER = null;
    private static String PATH_XML = null;

    public static void run(ConfigGenerator config) {
        if (config == null) {
            throw new MybatisPlusException("ConfigGenerator is null");
        } else if (config.getIdType() == null) {
            throw new MybatisPlusException("ConfigGenerator IdType is null");
        }
        File gf = new File(config.getSaveDir());
        if (!gf.exists()) {
            gf.mkdir();
        }

        PATH_ENTITY = getFilePath(gf.getPath(), "entity");
        PATH_MAPPER = getFilePath(gf.getPath(), "mapper");
        PATH_XML = getFilePath(gf.getPath(), "xml");

        new AutoGenerator(config).generate();

        try {
            String osName = System.getProperty("os.name");
            if (osName != null) {

                if (osName.contains("Mac")) {
                    Runtime.getRuntime().exec("open" + config.getSaveDir());
                } else if (osName.contains("Windows")) {
                    Runtime.getRuntime().exec(
                            "cmd /c start " + config.getSaveDir());
                } else {
                    System.out.println("save dir:" + config.getSaveDir());
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("generate success !");
    }

    private void generate() {
        Connection conn = null;
        try {

            Class.forName(config.getDbDriverName());
            conn = DriverManager.getConnection(config.getDbUrl(),
                    config.getDbUser(), config.getDbPassword());

            List<String> tables = getTables(conn);
            Map<String, String> tableComments = getTableComment(conn);
            for (String table : tables) {
                List<String> columns = new ArrayList<String>();
                List<String> types = new ArrayList<String>();
                List<String> comments = new ArrayList<String>();
                Map<String, IdInfo> idMap = new HashMap<String, IdInfo>();
                ResultSet results = conn.prepareStatement(
                        "show full fields from " + table).executeQuery();
                while (results.next()) {
                    columns.add(results.getString("FIELD"));
                    types.add(results.getString("TYPE"));
                    comments.add(results.getString("COMMENT"));
                    String key = results.getString("KEY");
                    if ("PRI".equals(key)) {
                        boolean autoIncrement = false;
                        if ("auto_increment".equals(results.getString("EXTRA"))) {
                            autoIncrement = true;
                        }
                        idMap.put(results.getString("FIELD"), new IdInfo(key,
                                autoIncrement));
                    }
                }
                String beanName = getBeanName(table, config.isDbPrefix());
                String mapperName = beanName + "Mapper";
                buildEntityBean(columns, types, comments,
                        tableComments.get(table), idMap, table, beanName);
                buildMapper(beanName, mapperName);
                buildMapperXml(columns, types, comments, mapperName);

            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }


    private void buildMapper(String beanName, String mapperName)
            throws IOException {
        File mapperFile = new File(PATH_MAPPER, mapperName + ".java");
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(
                new FileOutputStream(mapperFile), "utf-8"));
        bw.write("package " + config.getMapperPackage() + ";");
        bw.newLine();
        bw.newLine();
        bw.write("import " + config.getEntityPackage() + "." + beanName + ";");
        bw.newLine();
        bw.write("import com.k.mybatisplus.mapper.AutoMapper;");
        bw.newLine();

        bw = buildClassComment(bw, mapperName + " build");
        bw.newLine();
        bw.write("public interface " + mapperName + " extends AutoMapper<"
                + beanName + "> {");
        bw.newLine();
        bw.newLine();

        bw.newLine();
        bw.write("}");
        bw.flush();
        bw.close();
    }


    private void buildMapperXml(List<String> columns, List<String> types,
                                List<String> comments, String mapperName) throws IOException {
        File mapperXmlFile = new File(PATH_XML, mapperName + ".xml");
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(
                new FileOutputStream(mapperXmlFile)));
        bw.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
        bw.newLine();
        bw.write("<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">");
        bw.newLine();
        bw.write("<mapper namespace=\"" + config.getMapperPackage() + "."
                + mapperName + "\">");
        bw.newLine();
        bw.newLine();
        buildSQL(bw, columns);
        bw.write("</mapper>");
        bw.flush();
        bw.close();
    }

    private void buildSQL(BufferedWriter bw, List<String> columns)
            throws IOException {
        int size = columns.size();
        bw.write("\t<!---->");
        bw.newLine();
        bw.write("\t<sql id=\"Base_Column_List\">");
        bw.newLine();
        bw.write("\t\t id,");
        for (int i = 1; i < size; i++) {
            bw.write(" " + columns.get(i));
            if (i != size - 1) {
                bw.write(",");
            }
        }
        bw.newLine();
        bw.write("\t</sql>");
        bw.newLine();
        bw.newLine();
    }


    private Map<String, String> getTableComment(Connection conn)
            throws SQLException {
        Map<String, String> maps = new HashMap<String, String>();
        PreparedStatement pstate = conn.prepareStatement("show table status");
        ResultSet results = pstate.executeQuery();
        while (results.next()) {
            maps.put(results.getString("NAME"), results.getString("COMMENT"));
        }
        return maps;
    }

    private String getBeanName(String table, boolean includePrefix) {
        StringBuilder sb = new StringBuilder();
        if (table.contains("_")) {
            String[] tables = table.split("_");
            int l = tables.length;
            int s = 0;
            if (includePrefix) {
                s = 1;
            }
            for (int i = s; i < 1; i++) {
                String temp = tables[i].trim();
                sb.append(temp.substring(0, 1).toUpperCase()).append(
                        temp.substring(1).toLowerCase());

            }
        } else {
            sb.append(table.substring(0, 1).toUpperCase()).append(
                    table.substring(1).toLowerCase());
        }
        return sb.toString();

    }

    private void buildEntityBean(List<String> columns, List<String> types,
                                 List<String> comments, String tableComment,
                                 Map<String, IdInfo> idMap, String table, String beanName)
            throws IOException {
        File beanFile = new File(PATH_ENTITY, beanName + ".java");
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(
                new FileOutputStream(beanFile)));
        bw.write("package " + config.getEntityPackage() + ";");
        bw.newLine();
        bw.newLine();
        bw.write("import java.io.Serializable;");
        bw.newLine();
        if (isDate(types)) {
            bw.write("import java.util.Date;");
            bw.newLine();
        }
        if (isDecimal(types)) {
            bw.write("import java.math.BigDecimal;");
            bw.newLine();
        }
        bw.newLine();
        if (config.getIdType() != IdType.ID_WORKER) {
            bw.write("import com.k.mybatisplus.annotations.IdType;");
            bw.newLine();
        }
        bw.write("com.k.mybatisplus.annotations.TableField;");
        bw.newLine();
        bw.write("import com.k.mybatisplus.annotations.TableId;");
        bw.newLine();
        bw.write("import com.k.mybatisplus.annotations.TableName;");
        bw.newLine();
        bw = buildClassComment(bw, tableComment);
        bw.newLine();
        bw.write("@TableName(value = \"" + table + "\")");
        bw.newLine();
        bw.write("public class " + beanName + " implements Serializable {");
        bw.newLine();
        bw.newLine();
        bw.write("\t@TableField(exist = false)");
        bw.newLine();
        bw.write("\tprivate static final long serialVersionUID = 1L;");
        bw.newLine();
        int size = columns.size();
        for (int i = 0; i < size; i++) {
            bw.newLine();
            bw.write("\t/** " + comments.get(i) + " */");
            bw.newLine();
            /*
             * �ж�ID ���ע�� <br> isLine �Ƿ�����»���
             */
            String column = columns.get(i);
            String field = processField(column);
            boolean isLine = column.contains("_");
            IdInfo idInfo = idMap.get(column);
            if (idInfo != null) {
                // @TableId(value = "test_id", type = IdType.AUTO_INCREMENT)
                bw.write("\t@TableId");
                String idType = toIdType();
                if (idInfo.isAutoIncrement()) {
                    System.err.println(" Table :{ " + table
                            + " } ID is Auto increment");
                    if (isLine) {
                        bw.write("(value = \"" + column + "\"");
                        if (idType != null) {
                            bw.write(", ");
                            bw.write(idType);
                        }
                        bw.write(")");
                    }
                } else {
                    if (isLine) {
                        bw.write("(value = \"" + column + "\"");
                        if (idType != null) {
                            bw.write(", ");
                            bw.write(idType);
                        }
                        bw.write(")");
                    } else if (idType != null) {
                        bw.write("(");
                        bw.write(idType);
                        bw.write(")");
                    }
                }
                bw.newLine();
            } else if (isLine) {
                // @TableField(value = "test_type", exist = false)
                bw.write("\t@TableField(value = \"" + column + "\")");
                bw.newLine();
            }
            bw.write("\tprivate " + processType(types.get(i)) + " " + field
                    + ";");
            bw.newLine();
        }

        /*
         * ����get �� set����
         */
        for (int i = 0; i < size; i++) {
            String _tempType = processType(types.get(i));
            String _tempField = processField(columns.get(i));
            String _field = _tempField.substring(0, 1).toUpperCase()
                    + _tempField.substring(1);
            bw.newLine();
            bw.write("\tpublic " + _tempType + " get" + _field + "() {");
            bw.newLine();
            bw.write("\t\treturn this." + _tempField + ";");
            bw.newLine();
            bw.write("\t}");
            bw.newLine();
            bw.newLine();
            bw.write("\tpublic void set" + _field + "(" + _tempType + " "
                    + _tempField + ") {");
            bw.newLine();
            bw.write("\t\tthis." + _tempField + " = " + _tempField + ";");
            bw.newLine();
            bw.write("\t}");
            bw.newLine();
        }

        bw.newLine();
        bw.write("}");
        bw.newLine();
        bw.flush();
        bw.close();
    }

    public String toIdType() {
        if (config.getIdType() == IdType.AUTO) {
            return "type = IdType.AUTO";
        } else if (config.getIdType() == IdType.INPUT) {
            return "type = IdType.INPUT";
        }
        return null;
    }

    private String processField(String field) {
        StringBuffer sb = new StringBuffer(field.length());
        String[] fields = field.split("_");
        String temp = null;
        sb.append(fields[0]);
        for (int i = 1; i < fields.length; i++) {
            temp = fields[i].trim();
            sb.append(temp.substring(0, 1).toUpperCase()).append(
                    temp.substring(1));
        }
        return sb.toString();
    }

    // ��ȡ���б�
    private List<String> getTables(Connection conn) throws SQLException {

        List<String> tables = new ArrayList<String>();
        PreparedStatement pstate = conn.prepareStatement("show tables");
        ResultSet results = pstate.executeQuery();
        while (results.next()) {
            tables.add(results.getString(1));
        }
        return tables;

    }

    /**
     * �ֶ�����ת��
     *
     * @param type �ֶ�����
     * @return
     */
    private String processType(String type) {
        if (type.indexOf("char") > -1) {
            return "String";
        } else if (type.indexOf("bigint") > -1) {
            return "Long";
        } else if (type.indexOf("int") > -1) {
            return "Integer";
        } else if (type.indexOf("date") > -1 || type.indexOf("timestamp") > -1) {
            return "Date";
        } else if (type.indexOf("text") > -1) {
            return "String";
        } else if (type.indexOf("bit") > -1) {
            return "Boolean";
        } else if (type.indexOf("decimal") > -1) {
            return "BigDecimal";
        } else if (type.indexOf("blob") > -1) {
            return "byte[]";
        } else if (type.indexOf("float") > -1) {
            return "Float";
        } else if (type.indexOf("double") > -1) {
            return "Double";
        }
        return null;
    }

    /**
     * �����������ע��
     *
     * @param bw
     * @param text
     * @return
     * @throws IOException
     */
    private BufferedWriter buildClassComment(BufferedWriter bw, String text)
            throws IOException {
        bw.newLine();
        bw.write("/**");
        bw.newLine();
        bw.write(" *");
        bw.newLine();
        bw.write(" * " + text);
        bw.newLine();
        bw.write(" *");
        bw.newLine();
        bw.write(" */");
        return bw;
    }

    /**
     * �ֶ��Ƿ�Ϊ����������
     *
     * @param types �ֶ������б�
     * @return
     */
    private boolean isDecimal(List<String> types) {
        int size = types.size();
        for (int i = 0; i < size; i++) {
            String type = types.get(i);
            if (type.indexOf("decimal") > -1) {
                return true;
            }
        }
        return false;
    }

    /**
     * �ֶ��Ƿ�Ϊ��������
     *
     * @param types �ֶ������б�
     * @return
     */
    private boolean isDate(List<String> types) {
        int size = types.size();
        for (int i = 0; i < size; i++) {
            String type = types.get(i);
            if (type.indexOf("date") > -1 || type.indexOf("timestamp") > -1) {
                return true;
            }
        }
        return false;
    }

    /**
     * �����ļ���ַ
     *
     * @param segment �ļ���ַƬ��
     * @return
     */
    private static String getFilePath(String savePath, String segment) {
        File folder = new File(savePath + File.separator + segment);
        if (!folder.exists()) {
            folder.mkdir();
        }
        return folder.getPath();
    }

    class IdInfo {
        private String value;
        private boolean autoIncrement;

        public String getValue() {
            return value;
        }

        public void setValue(String value) {
            this.value = value;
        }

        public boolean isAutoIncrement() {
            return autoIncrement;
        }

        public void setAutoIncrement(boolean autoIncrement) {
            this.autoIncrement = autoIncrement;
        }

        public IdInfo(String value, boolean autoIncrement) {
            super();
            this.value = value;
            this.autoIncrement = autoIncrement;
        }

    }
}
